Mr.Lee-李健博的博客

[16-史上最快的日志库-zap]

2022-05-08

[16-史上最快的日志库-zap]

一 Zap介绍

Zap 是Uber推出,非常快的、结构化的,分日志级别的 Go 日志库

无反射,零分配的JSON编码器,基本记录器尽可能避免序列化开销和分配

项目地址:https://github.com/uber-go/zap

官方文档:https://pkg.go.dev/go.uber.org/zap

Stars数量:20.3k

速度比较

image-20220422031115531

二 快速使用

zap提供了两种日志记录器

go get -u go.uber.org/zap

2.1 SugaredLogger

加了糖的 Logger

在性能很好但不是很关键的环境中,使用SugaredLogger。它比其他结构化日志包快4-10倍,并且包含结构化和printf风格的api。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"go.uber.org/zap"
"time"
)

func main() {
//1 创建一个Production(生产环境)的 logger
logger, _ := zap.NewProduction()
//2 flushes buffer, if any, 刷新缓冲区,存盘
defer logger.Sync()
// 3 创建Sugar的logger
sugar := logger.Sugar()
//4 输出日志
sugar.Info("info--普通格式日志")
sugar.Infof("info--格式化字符串格式日志: %s", "lqz")
sugar.Infow("info---松散类型的键值对格式日志",
// 结构化上下文为松散类型的键值对,随便写键值对
"name", "lqz",
"attempt", 3,
"backoff", time.Second,
)
}

2.2 Logger

当性能和类型安全至关重要时,使用Logger。它甚至比SugaredLogger还要快,并且分配的数量要少得多,但是它只支持结构化日志 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"go.uber.org/zap"
"time"
)

func main() {
// 看源码,默认基本是InfoLevel,所有Debug不会打印
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("info--松散类型的键值对格式日志",
// 作为强类型字段值的结构化上下文.
zap.String("name", "lqz"),
zap.Int("age", 19),
zap.Duration("backoff", time.Second),
)
logger.Error("error--松散类型的键值对格式日志",
zap.String("name", "lqz"),
zap.Int("age", 19),
zap.Duration("backoff", time.Second),)


}

2.3 如何选择logger

在Logger和SugaredLogger之间进行选择不需要在应用程序范围内进行决定:在两者之间进行转换既简单又便宜。从上面就可以看出来,二者创建使用区别很小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"go.uber.org/zap"
)

func main() {
logger := zap.NewExample()
defer logger.Sync()
sugar := logger.Sugar() // 通过logger得到Sugar
plain := sugar.Desugar()// 通过Sugar得到logger
sugar.Info("info-->sugar")
plain.Info("info-->logger")
}

2.4 日志级别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//const 文档下面有介绍日志级别的定义,7个日志级别

const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel = zapcore.DebugLevel
// InfoLevel is the default logging priority.
InfoLevel = zapcore.InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel = zapcore.WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel = zapcore.ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel = zapcore.DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel = zapcore.PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel = zapcore.FatalLevel
)

三 高级使用

3.1 初始化 looger

在文档的 Configuring Zap中:
构建Logger最简单的方法是使用zap固有的预设:

NewExampleNewProductionNewDevelopment

三者创建的 logger 是有区别的, 我们可以在官方文档的 type logger下面找到三个函数的介绍, 对应不同的场景。

NewExample

1
2
func NewExample(options ...Option) *Logger
NewExample构建了一个专门为zap的可测试示例设计的Logger。它将DebugLevel及以上的日志作为JSON写入标准输出,但省略了时间戳和调用函数,以保持示例输出的简短和确定性

NewProduction

1
2
3
4
func NewProduction(options ...Option) (*Logger, error)

NewProduction构建了一个合理的生产日志记录器,它将infollevel及以上的日志以JSON的形式写入标准错误。
它是NewProductionConfig(). build(…Option)的快捷方式。

NewDevelopment

1
2
3
4
5
func NewDevelopment(options ...Option) (*Logger, error)

NewDevelopment构建一个开发日志记录器,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。
这是NewDevelopmentConfig().Build(…选项)的快捷方式
就好比去商店卖商品,初上自带了几个配置好的模式。 通过配置生成对应的 logger。 我们也可以自定义 配置,生成自己自定义的 logger

3.2 方法使用

在文档的 types/loggertypes/SaguredLogger 里面记录了相关的looger记录消息的使用方法

以 logger为例:

1
2
//1 都接受一个 msg String, 后面是可选的一些字段。Field 类型,可以查看文档有很多的类型。
//2 写一个Get请求访问相应的网址,记录日志信息

image-20220502154810858

NewProduction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"go.uber.org/zap"
"net/http"
"time"
)

func main() {
// 创建一个 logger, 可以选择其他预置创建,会有不同的输出效果
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
url:="https://www.cnblogs.com/liuqingzheng"
res,err:=http.Get(url)
if err != nil {
logger.Error("访问博客失败",
zap.String("url",url), // 字符串形式field
zap.Error(err)) // 错误形式field
}else {
logger.Info("访问博客成功",
zap.String("url",url), // 字符串形式field
zap.Int("status",res.StatusCode),// int类型字段
zap.Duration("backoff",time.Second*3)) // Duration类型字段,只是用来做测试,无特殊含义
res.Body.Close()
}
}
// 成功
{"level":"info","ts":1651478232.032387,"caller":"go_test_learn/s20.go:20","msg":"访问博客成功","url":"https://www.cnblogs.com/liuqingzheng"us":200,"backoff":3}
// 失败
{"level":"error","ts":1651478317.474609,"caller":"go_test_learn/s20.go:16","msg":"访问博客失败","url":"https://www.cnblogs.cm/liuqingzheng"r":"Get \"https://www.cnblogs.cm/liuqingzheng\": dial tcp: lookup www.cnblogs.cm: no such host","stacktrace":"main.main\n\t/Users/liuqingzheng/go/src/go_test_learn/s20.go:16\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:255"}

注意

可以看到执行程序后终端提示相关的信心。 msg 就是自己设置的,时间, url等。 还有一个 caller 调用者信息,指明问题出现的行数

NewDevelopment()

创建的生成日志是这样的: 空格隔开, 缺少调用者

1
2022-05-02T16:01:00.202+0800    INFO    go_test_learn/s20.go:21 访问博客成功    {"url": "https://www.cnblogs.com/liuqingzheng", "status": 200, "backoff": "3s"}

NewExample()

1
{"level":"info","msg":"访问博客成功","url":"https://www.cnblogs.com/liuqingzheng","status":200,"backoff":"3s"}

3.3 定制 logger

查看NewProduction 的源码,实际底层就是: NewProductionConfig().Build(options…)

1
2
3
4
5
6
7
func NewProduction(options ...Option) (*Logger, error) {
//调用了 NewProductionConfig()方法,内部初始化创建,返回了一个 Config 对象
//Build, 内部通过 Config对象的配置, 利用New方法生成相应的 logger对象,并返回
return NewProductionConfig().Build(options...)
}

// 这是 zap库给我们预置的 NewProduction()等方法,内部是按照指定的配置,生成相应的 logger 日志对象。 我们也可以自己调用内部的相关方法, 模仿 NewProductionConfig().Build(options…) 相关过程,自己创建,定制化 logger对象。

观察New方法 生成logger 所需要的东西。在Build 函数中:

1
2
3
4
5
6
7
8
9
// 返回一个 Core对象, 需要的是三个参数
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core
// 返回一个Logger指针
func New(core zapcore.Core, options ...Option) *Logger
log := New(
zapcore.NewCore(enc, sink, cfg.Level),
cfg.buildOptions(errSink)...,
)
//Core是一个最小的、快速的记录器接口。它是为库作者设计的,用来封装更友好的API

关于 NewProductionConfig()函数, 返回对应的Config 对象,Build 函数根据这个配置,进行生成 logger对象。

我们可以自定义这个, 来实现生成自己的logger下面看下它的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// NewProductionConfig是一个合理的生产日志配置。
//在infollevel及以上级别启用日志记录。
//它使用JSON编码器,写入标准错误,并启用采样。
// stacktrace会自动包含在ErrorLevel及以上的日志中。
func NewProductionConfig() Config {
return Config{
// 日志级别
Level: NewAtomicLevelAt(InfoLevel),
Development: false,
Sampling: &SamplingConfig{
Initial: 100,
Thereafter: 100,
},
// 编码方式
Encoding: "json",
// EncoderCofig, 配置 encoder 编辑器的默认配置。
EncoderConfig: NewProductionEncoderConfig(),
// 打开的文件, 写入日志信息到这里。
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}

写入文件

方式一

  • 按照上面的自定义 logger, 创建核心Core需要三个参数,其中就有控制 写入文件的

  • Encoder: 编辑器。提供了两种信息的编辑方式

    • 需要传递的参数,可以使用 默认的EncoderConfig: NewProductionEncoderConfig() 传递进去。
    • 一种 文本样式的, 一种 json 样式的信息输入。
    • image-20220502162553648
  • WriteSyncer指定日志写到哪里。可以定义自己指定的文件路径

    • image-20220502162755449

    • 通过func AddSync(w io.Writer) WriteSyncer 方法,返回一个。
      AddSync用于转换io。Writer到WriteSyncer。它是智能的:Writer实现了WriteSyncer,我们将使用现有的Sync方法。如果没有,我们将添加一个无操作同步。

      1
      2
      3
      4
      5
      // 创建文件对象
      file, _ := os.Create("./getLog.log") // 或者是用 OpenFile函数,在原来基础上追加。
      // file, _ := os.OpenFile("./getLog.log", os.O_APPEND | os.O_RDWR, 0744)
      // 生成 WriteSyncer
      wSy := zapcore.AddSync(file)
  • LevelEnabler设置哪种级别的日志将被写入

    • 对应的就是前面介绍的日志级别;如:
      zapcore.DebugLevel
  • 创建自定义logger:

    • 根据上面三点参数的理解,就可以指定文件建立了

    • ```go
      // 还剩一个后面的配置信息没有传入,但是已经可以了
      // 默认我们调用 NewProduction()方法也是没有传递啥配置进去的。
      log := New(
      zapcore.NewCore(传递编辑器(两种), 自定义文件输出, cfg.Level(级别)),
      )

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      - 按照这样的建立完成之后就可以使用了,往指定文件里打印日志。

      ```go
      func initLogger2() *zap.Logger {
      //1 日志输出路径
      //file, _ := os.Create("./test2.log")
      file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
      // 把文件对象做成WriteSyncer类型
      writeSyncer := zapcore.AddSync(file)
      // 2 encoder编码,就两种
      //encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
      encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())

      // 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
      core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
      // 4 创建logger对象
      logger := zap.New(core)
      return logger
      }

    方式二

    还有 zap 预置的生成 logger的方式,都是通过 NewProductionConfig() 来生成相关配置的, 自定义一个 NewProductionConfig() 然后,按着相应的步骤就可以了。 Build方法 就是通过 配置的 Config 对象,来生成的 logger。

    重写方法,只需要加个文件名就可以了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func initLogger() *zap.Logger {
    // 1 得到config对象
    conf := zap.NewProductionConfig()
    // 2 修改config对象的属性,如编码,输出路径等
    //conf.Encoding="console"
    conf.Encoding = "json"
    //conf.OutputPaths = append(conf.OutputPaths, "./test.log")
    conf.OutputPaths = []string{"./test.log"}
    //3 通过config对象得到logger对象指针
    logger, _ := conf.Build()
    return logger
    }

更改时间编码

image-20220502230547910

方式一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func initLogger() *zap.Logger {
// 1 得到config对象
conf := zap.NewProductionConfig()
// 2 修改config对象的属性,如编码,输出路径等
//conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./test.log"}

//3 更改时间 人类可读
conf.EncoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder
//3 通过config对象得到logger对象指针
logger, _ := conf.Build()
return logger
}

方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func initLogger2() *zap.Logger {
//1 日志输出路径
//file, _ := os.Create("./test2.log")
file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(file)
// 2 encoder编码,就两种
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewConsoleEncoder(encoderConfig)

// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 4 创建logger对象
logger := zap.New(core)
return logger
}

增加调用者信息

根据时间的理解, 这里很容易想到: 配置项当中的 EncodeCaller, 也可以指定相关的函数,用来打印 调用者的信息。

方式一(该方式本身就有,不需要再加)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func initLogger() *zap.Logger {
// 1 得到config对象
conf := zap.NewProductionConfig()
// 2 修改config对象的属性,如编码,输出路径等
//conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./test.log"}

//3 更改时间 人类可读
conf.EncoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder

//4 增加调用者信息(该方式本身就有)
//3 通过config对象得到logger对象指针
logger, _ := conf.Build()
return logger
}

还有一种方式是: 我们创建Core, zap.New()创建 logger, 分析第二个参数的 Options 类型, 文档中可以找到相关的方法,就有添加调试 显示调用人信息的方法。

方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func initLogger2() *zap.Logger {
//1 日志输出路径
//file, _ := os.Create("./test2.log")
file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(file)
// 2 encoder编码,就两种
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewConsoleEncoder(encoderConfig)

// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 4 创建logger对象
//logger := zap.New(core)
logger := zap.New(core, zap.AddCaller())
return logger
}

四 使用 Lumberjack 进行日志切割归档

Zap 本身不支持切割归档日志文件,为了添加日志切割归档功能,我们将使用第三方库 Lumberjack 来实现。

1
go get -u github.com/natefinch/lumberjack

lumberjack.Logger 实现了 io.writer 接口,可以作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test.log", // Filename: 日志文件的位置
MaxSize: 10, // 在进行切割之前,日志文件的最大大小(以 MB 为单位)
MaxBackups: 5, // 保留旧文件的最大个数
MaxAge: 30, // 保留旧文件的最大天数
Compress: false, // 是否压缩 / 归档旧文件
}
return zapcore.AddSync(lumberJackLogger)
}

/*
Lumberjack Logger 采用以下属性作为输入:
● Filename: 日志文件的位置
● MaxSize:在进行切割之前,日志文件的最大大小(以 MB 为单位)
● MaxBackups:保留旧文件的最大个数
● MaxAges:保留旧文件的最大天数
● Compress:是否压缩 / 归档旧文件
*/

4.1 代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

func initLogger2() *zap.Logger {
//1 日志输出路径
//file, _ := os.Create("./test2.log")
//file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(getLogWriter())
// 2 encoder编码,就两种
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewConsoleEncoder(encoderConfig)

// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 4 创建logger对象
//logger := zap.New(core)
logger := zap.New(core, zap.AddCaller())
return logger
}
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test3.log", // 导入文件名
MaxSize: 1, // 大小M兆
MaxBackups: 5, // 最大备份数量
MaxAge: 30, // 最大备份天数
Compress: false, // 是否压缩
}
return zapcore.AddSync(lumberJackLogger)
}
func main() {
logger := initLogger2()
defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
for i:=0;i<100000;i++{
logger.Info("测试日志分隔")
}
}

image-20220502235135414

五 Gin中使用zap日志库

5.1 使用第三方库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 地址:https://github.com/gin-contrib/zap

// 下载
go get github.com/gin-contrib/zap
package main

import (
"fmt"
ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"time"
)

//func main() {
// // 初始化logger
// logger.InitLogger()
// r:=gin.New()
// // 两个中间件加入gin中
// r.Use(logger.GinLogger,logger.GinRecovery(true))
//
// r.GET("/", func(c *gin.Context) {
// // 以后直接使用zap.L()即可,并且有锁,不存在并发安全的问题
// // 1 正常访问
// //zap.L().Info("info-日志记录了")
// //c.String(200,"hello logger insert")
// // 2 抛异常
// panic("出错了宝贝")
// c.String(200,"hello logger insert")
// })
// r.Run(":8080")
//
//}


func main() {
r := gin.New()

//logger, _ := zap.NewProduction()
// 自定义写到文件中
// 1 得到config对象
conf := zap.NewProductionConfig()
// 2 修改config对象的属性,如编码,输出路径等
//conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./web.log"}
//3 通过config对象得到logger对象指针
logger, _ := conf.Build()
//4 替换掉全局的logger,以后都使用zap.L()
zap.ReplaceGlobals(logger)

// Add a ginzap middleware, which:
// - Logs all requests, like a combined access and error log.
// - Logs to stdout.
// - RFC3339 with UTC time format.
r.Use(ginzap.Ginzap(logger, time.RFC3339, true))

// Logs all panic to error log
// - stack means whether output the stack info.
r.Use(ginzap.RecoveryWithZap(logger, true))

r.GET("/", func(c *gin.Context) {
zap.L().Info("info-test")
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
})

// Example when panic happen.
r.GET("/panic", func(c *gin.Context) {
panic("An unexpected error happen!")
})

r.Run(":8080")
}

5.2 自己定制

讲解了 gin.Default 创建引擎, 默认的添加了两个中间件。 一个是 logger 日志,一个是 recover 恢复。 gin 自带的 logger 就是在这里实现起作用的
那么我们也需要将 zap封装为中间件–>GinLogger和GinRecovery

logger/logger.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package logger

import (
"github.com/gin-gonic/gin"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"time"
)

// 1 定义一下logger使用的常量
const (
mode = "dev" //开发模式
filename = "web_app.log" // 日志存放路径
//level = "debug" // 日志级别
level = zapcore.DebugLevel // 日志级别
max_size = 200 //最大存储大小
max_age = 30 //最大存储时间
max_backups = 7 //#备份数量
)

// 2 初始化Logger对象
func InitLogger() (err error) {
// 创建Core三大件,进行初始化
writeSyncer := getLogWriter(filename, max_size, max_backups, max_age)
encoder := getEncoder()
// 创建核心-->如果是dev模式,就在控制台和文件都打印,否则就只写到文件中
var core zapcore.Core
if mode == "dev" {
// 开发模式,日志输出到终端
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// NewTee创建一个核心,将日志条目复制到两个或多个底层核心中。
core = zapcore.NewTee(
zapcore.NewCore(encoder, writeSyncer, level),
zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), level),
)
} else {
core = zapcore.NewCore(encoder, writeSyncer, level)
}

//core := zapcore.NewCore(encoder, writeSyncer, level)
// 创建 logger 对象
log := zap.New(core, zap.AddCaller())
// 替换全局的 logger, 后续在其他包中只需使用zap.L()调用即可
zap.ReplaceGlobals(log)
return
}

// 获取Encoder,给初始化logger使用的
func getEncoder() zapcore.Encoder {
// 使用zap提供的 NewProductionEncoderConfig
encoderConfig := zap.NewProductionEncoderConfig()
// 设置时间格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 时间的key
encoderConfig.TimeKey = "time"
// 级别
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 显示调用者信息
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// 返回json 格式的 日志编辑器
return zapcore.NewJSONEncoder(encoderConfig)
}

// 获取切割的问题,给初始化logger使用的
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
// 使用 lumberjack 归档切片日志
lumberJackLogger := &lumberjack.Logger{
Filename: filename,
MaxSize: maxSize,
MaxBackups: maxBackup,
MaxAge: maxAge,
}
return zapcore.AddSync(lumberJackLogger)
}

// GinLogger 用于替换gin框架的Logger中间件,不传参数,直接这样写
func GinLogger(c *gin.Context) {
logger := zap.L()
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next() // 执行视图函数
// 视图函数执行完成,统计时间,记录日志
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)

}

// GinRecovery 用于替换gin框架的Recovery中间件,因为传入参数,再包一层
func GinRecovery(stack bool) gin.HandlerFunc {
logger := zap.L()
return func(c *gin.Context) {
defer func() {
// defer 延迟调用,出了异常,处理并恢复异常,记录日志
if err := recover(); err != nil {
// 这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------开始--------
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
//httputil包预先准备好的DumpRequest方法
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// 如果连接已断开,我们无法向其写入状态
c.Error(err.(error))
c.Abort()
return
}
// 这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------结束--------

// 是否打印堆栈信息,使用的是debug.Stack(),传入false,在日志中就没有堆栈信息
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
// 有错误,直接返回给前端错误,前端直接报错
//c.AbortWithStatus(http.StatusInternalServerError)
// 该方式前端不报错
c.String(200,"访问出错了")
}
}()
c.Next()
}
}

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"github.com/gin-gonic/gin"
"go_test_learn/logger"
)

func main() {
// 初始化logger
logger.InitLogger()
r:=gin.New()
// 两个中间件加入gin中
r.Use(logger.GinLogger,logger.GinRecovery(true))

r.GET("/", func(c *gin.Context) {
// 以后直接使用zap.L()即可,并且有锁,不存在并发安全的问题
// 1 正常访问
//zap.L().Info("info-日志记录了")
//c.String(200,"hello logger insert")
// 2 抛异常
panic("出错了宝贝")
c.String(200,"hello logger insert")
})
r.Run(":8080")

}
使用支付宝打赏
使用微信打赏

点击上方按钮,请我喝杯咖啡!

扫描二维码,分享此文章